/**
 * @file dxNfcCard.js
 * @module dxNfcCard
 * @description
 * NFC module based on the native nfc_bridge C library.
 * This module provides a singleton interface for interacting with NFC cards.
 * It supports detecting, reading, and writing various basic NFC card types,
 * including M1, CPU, and NTAG cards.
 *
 * Additionally, the module integrates eID (Chinese 2nd Generation ID card) reading functionality.
 * **Note**: The eID feature requires a network connection and currently only supports Chinese 2nd Generation ID cards.
 *
 * @usage
 * // 1. Import the module
 * import { dxNfcCard } from 'dxNfcCard.js';
 *
 * // 2. Initialize
 * dxNfcCard.init();
 *
 * // 3. (Optional) Initialize eID functionality
 * dxNfcCard.eidInit({
 *   config: {
 *     device_model: "your_device_model"
 *   }
 * });
 *
 * // 4. Set callbacks
 * dxNfcCard.setCallbacks({
 *   onCardDetected: (cardInfo) => { console.log('NFC Card:', cardInfo); },
 *   onEidDetected: (eidInfo) => { console.log('eID Card:', eidInfo); }
 * });
 *
 * // 5. Poll for events in a timer
 * setInterval(dxNfcCard.loop, 100);
 *
 * // 6. Call specific operations, e.g., read M1 block
 * // const data = dxNfcCard.m1ReadBlock(...);
 */
import { nfc } from './libvbar-m-dxnfccard.so';

const dxNfcCard = {};

let _callbacks = {};

/**
 * Initializes the NFC module. Must be called before any other operations.
 * @returns {void}
 */
dxNfcCard.init = function () {
    nfc.init();
};

/**
 * Deinitializes the NFC module and releases all resources.
 * @returns {void}
 */
dxNfcCard.deinit = function () {
    nfc.deinit();
};

/**
 * @typedef {object} NfcConfig
 * @property {boolean} afi_enable - Enable Application Family Identifier.
 * @property {number} afi - Application Family Identifier value.
 * @property {number} card_protocol - Bitmask for card protocols to support.
 * @property {boolean} identity_card_enable - Enable reading of identity cards.
 * @property {number} read_timeout_ms - Read timeout in milliseconds.
 * @property {number} braud_tx - TX baud rate.
 * @property {number} braud_rx - RX baud rate.
 * @property {boolean} i14443p4_switch - Enable ISO14443-4 protocol.
 * @property {number} felica_braud - FeliCa baud rate.
 * @property {number} nfc_sak28 - SAK value handling for specific cards.
 * @property {number} felica_syscode - FeliCa system code.
 * @property {number} felica_request_code - FeliCa request code.
 */

/**
 * Retrieves the current NFC configuration.
 * @returns {NfcConfig} The current configuration object.
 */
dxNfcCard.getConfig = function () {
    return nfc.getConfig();
};

/**
 * Updates the NFC configuration.
 * Not neccecary to modify, use default values unless special requirements.
 * @param {Partial<NfcConfig>} config - A configuration object with properties to update.
 * @returns {void}
 */
dxNfcCard.updateConfig = function (config) {
    return nfc.updateConfig(config);
};

/**
 * Checks if a card is currently present in the NFC field.
 * @returns {boolean} True if a card is present, false otherwise.
 */
dxNfcCard.isCardIn = function () {
    return nfc.isCardIn();
};

/**
 * Reads a 16-byte block from an M1 card.
 * @param {number} blockNumber - The block number to read (0-63 for 1K, 0-255 for 4K).
 * @param {ArrayBuffer} key - A 6-byte ArrayBuffer containing the key (A or B).
 * @param {number} keyType - The key type: 0x60 for Key A, 0x61 for Key B.
 * @param {number} [taskFlag=0] - Optional task flag for card selection (0: AUTO, 1: ACTIVE, 2: IDLE).
 * @returns {ArrayBuffer} A 16-byte ArrayBuffer containing the block data.
 */
dxNfcCard.m1ReadBlock = function (blockNumber, key, keyType, taskFlag = 0x00) {
    return nfc.m1ReadBlock(blockNumber, key, keyType, taskFlag);
};

/**
 * Writes a 16-byte block to an M1 card.
 * @param {number} blockNumber - The block number to write.
 * @param {ArrayBuffer} data - A 16-byte ArrayBuffer containing the data to write.
 * @param {ArrayBuffer} key - A 6-byte ArrayBuffer containing the key (A or B).
 * @param {number} keyType - The key type: 0x60 for Key A, 0x61 for Key B.
 * @param {number} [taskFlag=0] - Optional task flag for card selection.
 * @returns {number} 0 on success.
 */
dxNfcCard.m1WriteBlock = function (blockNumber, data, key, keyType, taskFlag = 0x00) {
    return nfc.m1WriteBlock(blockNumber, data, key, keyType, taskFlag);
};

/**
 * Reads one or more blocks from a sector of an M1 card.
 * @param {number} sectorNum - The sector number to read from (0-39).
 * @param {number} logicBlkNum - The starting block number within the sector (0-3 for small sectors, 0-15 for large sectors).
 * @param {number} blkCount - The number of blocks to read.
 * @param {ArrayBuffer} key - A 6-byte ArrayBuffer for authentication.
 * @param {number} keyType - The key type (0x60 for Key A, 0x61 for Key B).
 * @param {number} [taskFlag=0] - Optional task flag for card selection.
 * @returns {ArrayBuffer} An ArrayBuffer containing the data read from the sector.
 */
dxNfcCard.m1ReadSector = function (sectorNum, logicBlkNum, blkCount, key, keyType, taskFlag = 0x00) {
    return nfc.m1ReadSector(sectorNum, logicBlkNum, blkCount, key, keyType, taskFlag);
};

/**
 * Writes one or more blocks to a sector of an M1 card.
 * @param {number} sectorNum - The sector number to write to (0-39).
 * @param {number} logicBlkNum - The starting block number within the sector.
 * @param {ArrayBuffer} data - Data to write. The length must be a multiple of 16. The number of blocks to write is determined by data.length / 16.
 * @param {ArrayBuffer} key - A 6-byte ArrayBuffer for authentication.
 * @param {number} keyType - The key type (0x60 for Key A, 0x61 for Key B).
 * @param {number} [taskFlag=0] - Optional task flag for card selection.
 * @returns {number} The number of bytes written on success.
 */
dxNfcCard.m1WriteSector = function (sectorNum, logicBlkNum, data, key, keyType, taskFlag = 0x00) {
    return nfc.m1WriteSector(sectorNum, logicBlkNum, data, key, keyType, taskFlag);
};

/**
 * Reads the version information from an NTAG card.
 * @returns {ArrayBuffer} An ArrayBuffer containing the version data.
 */
dxNfcCard.ntagReadVersion = function () {
    return nfc.ntagReadVersion();
};

/**
 * Reads 4 pages (16 bytes) from an NTAG card, starting at the specified page number.
 * @param {number} pageNum - The starting page number to read from.
 * @returns {ArrayBuffer} A 16-byte ArrayBuffer containing the data from 4 pages.
 */
dxNfcCard.ntagReadPage = function (pageNum) {
    return nfc.ntagReadPage(pageNum);
};

/**
 * Writes one page (4 bytes) to an NTAG card.
 * @param {number} pageNum - The page number to write to.
 * @param {ArrayBuffer} data - A 4-byte ArrayBuffer containing the data to write.
 * @returns {void}
 */
dxNfcCard.ntagWritePage = function (pageNum, data) {
    return nfc.ntagWritePage(pageNum, data);
};

/**
 * Reads a range of pages from an NTAG card quickly.
 * @param {number} startPage - The starting page number.
 * @param {number} endPage - The ending page number.
 * @returns {ArrayBuffer} An ArrayBuffer containing the data from the specified page range.
 */
dxNfcCard.ntagFastReadPage = function (startPage, endPage) {
    return nfc.ntagFastReadPage(startPage, endPage);
};

/**
 * Sends an ISO14443-A APDU command to the card.
 * @param {ArrayBuffer} command - The APDU command to send.
 * @param {number} [taskFlag=0] - Optional task flag for card selection.
 * @returns {ArrayBuffer} An ArrayBuffer containing the APDU response from the card.
 */
dxNfcCard.iso14443Apdu = function (command, taskFlag = 0x00) {
    return nfc.iso14443Apdu(command, taskFlag);
};

/**
 * Initializes the eID (electronic ID) reading functionality.
 * After calling this, swiping a supported ID card will trigger the `onEidDetected` callback.
 * This function requires an active network connection.
 * @param {object} [options] - Configuration for the eID service. Can be omitted to use default values.
 * @param {string} [options.ip="deviceid.dxiot.com"] - The IP address of the eID server.
 * @param {number} [options.port=9889] - The port of the eID server.
 * @param {object} options.config - Advanced eID parameters.
 * @param {number} [options.config.read_len=54] - Read length per transaction, default is 0x36 (54).
 * @param {number} [options.config.declevel=2] - Whether to read the photo (1: no, 2: yes).
 * @param {number} [options.config.loglevel] - Log level (0, 1, or 2).
 * @param {number} [options.config.model=0] - 0: return ID info, 1: return reqid for forwarding.
 * @param {number} [options.config.type=0] - Card type (0: ID card, 1: e-license).
 * @param {number} [options.config.pic_type=1] - Photo data type (0: wlt, 1: jpg).
 * @param {number} [options.config.envCode=52302] - Environment code.
 * @param {string} options.config.device_model - Device model.
 * @param {number} [options.config.info_type=0] - Return type (0: struct, 1: raw data).
 * @returns {void}
 * @throws {Error} If required config properties are missing.
 */
dxNfcCard.eidInit = function (options = { ip: "deviceid.dxiot.com", port: 9889 }) {
    if (!options.config ||
        typeof options.config.device_model !== 'string') {
        throw new Error("eidInit requires options.config with string properties: device_model.");
    }
    return nfc.eidInit(options);
}

/**
 * Activates the eID module using an activation code and device information.
 * This is typically done once to bind the device to the eID service.
 * This function requires an active network connection.
 * @param {object} options - Activation options.
 * @param {string} options.codeMsg - The activation code message.
 * @param {string} options.version - Firmware version.
 * @param {string} options.macAddr - Device MAC address.
 * @returns {number} 0 on success, negative value on failure.
 * @throws {Error} If any of the required options are missing or invalid.
 */
dxNfcCard.eidActive = function (options) {
    if (!options ||
        typeof options.codeMsg !== 'string' ||
        typeof options.version !== 'string' ||
        typeof options.macAddr !== 'string') {
        throw new Error("eidActive requires an object with string properties: codeMsg, version, and macAddr.");
    }
    return nfc.eidActive(options);
}

/**
 * Deinitializes the eID functionality.
 * @returns {void}
 */
dxNfcCard.eidDeinit = function () {
    return nfc.eidDeinit();
}

/**
 * Sets callback handlers for NFC events.
 * @typedef {object} CardInfo
 * @property {number} card_type - The type of the card.
 * @property {string} id - The card's unique identifier as a hex string.
 * @property {number} id_len - The length of the card's ID in bytes.
 * @property {number} type - Detailed card type.
 * @property {number} sak - SAK value (Select Acknowledge).
 * @property {number} timestamp - The timestamp of the event (system time).
 * @property {number} monotonic_timestamp - The monotonic timestamp of the event.
 *
 * @typedef {object} EidInfo
 * @property {string} name - Full name.
 * @property {string} sex - Sex / Gender.
 * @property {string} nation - Ethnic group.
 * @property {string} birthday - Date of birth (Format: YYYYMMDD).
 * @property {string} address - Residential address.
 * @property {string} idCardNo - Citizen identification number.
 * @property {string} grantDept - Issuing authority.
 * @property {string} userLifeBegin - Valid from date (Format: YYYYMMDD).
 * @property {string} userLifeEnd - Valid until date (Format: YYYYMMDD or "long" for long-term).
 * @property {string} picture - Base64 encoded string of the ID photo.
 *
 * @param {object} callbacks - An object containing callback functions.
 * @param {function(cardInfo: CardInfo)} [callbacks.onCardDetected] - Called when a standard NFC card is detected.
 * @param {function(eidInfo: EidInfo)} [callbacks.onEidDetected] - Called when an eID (ID card) is successfully read.
 * @returns {void}
 */
dxNfcCard.setCallbacks = function (callbacks) {
    _callbacks = callbacks;
};

/**
 * Processes events from the NFC event queue. Should be called periodically (e.g., in setInterval).
 * @example
 * setInterval(() => {
 *   try {
 *     dxNfcCard.loop();
 *   } catch (e) {
 *     logger.error('Error in NFC loop:', e);
 *   }
 * }, 50); // Process events every 50ms
 */
dxNfcCard.loop = function () {
    // Process regular card events
    let card_ev = nfc.getEvent();
    if (card_ev && _callbacks.onCardDetected) {
        _callbacks.onCardDetected(card_ev);
    }

    // Process eID events
    let eid_ev_str = nfc.getEidEvent();
    if (eid_ev_str && _callbacks.onEidDetected) {
        const eid_info = JSON.parse(eid_ev_str);
        _callbacks.onEidDetected(eid_info);
    }
};

/**
 * Gets the native NFC client object.
 * @returns {object} The native client object.
 */
dxNfcCard.getNative = function () {
    return nfc;
};

/**
 * @readonly
 * @enum {number}
 * Enum for card types.
 */
dxNfcCard.CARD_TYPE = Object.freeze({
    /** Unspecified Type A protocol card */
    TYPE_A: 64,
    /** Mifare Ultralight card */
    MIFARE_ULTRALIGHT: 65,
    /** Mifare Classic EV1 1K (4-byte UID) */
    MIFARE_CLASSIC_1K_4B: 66,
    /** Mifare Classic EV1 4K */
    MIFARE_CLASSIC_4K: 67,
    /** CPU card (Type A protocol) */
    CPU_A: 68,
    /** Mifare Desfire series */
    MIFARE_DESFIRE: 69,
    /** Physical ID card (Type B protocol) */
    IDENTITY_CARD: 70,
    /** ISO15693 protocol card (long-range IC card) */
    ISO15693: 71,
    /** Unspecified Type B protocol card */
    TYPE_B: 74,
    /** CPU card (Type B protocol) */
    CPU_B: 75,
    /** Generic M1 card (sometimes synonymous with 66) */
    M1: 76,
    /** Japanese Felica card (Sony standard) */
    FELICA: 77,
    /** Mifare Plus series */
    MIFARE_PLUS: 78,
    /** Also an ID card (sometimes an IDCard type read by the logical layer or CPU) */
    IDCARD: 97,
    /** Unsupported or unrecognized card */
    NOT_SUPPORT: 127,
});

export default dxNfcCard;
